/*
 * Copyright (c) 2013, SRI International
 * All rights reserved.
 * Licensed under the The BSD 3-Clause License;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 * 
 * http://opensource.org/licenses/BSD-3-Clause
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the aic-web-praise nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.sri.ai.lpitools.gwtgui.client.components.admin;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.cell.client.ButtonCell;
import com.google.gwt.cell.client.CheckboxCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.SimpleLayoutPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.user.client.ui.StackLayoutPanel;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sri.ai.lpitools.cobra.gwtutil.dataobjects.shared.EvidenceInfo;
import com.sri.ai.lpitools.cobra.gwtutil.dataobjects.shared.ModelInfo;
import com.sri.ai.lpitools.cobra.gwtutil.dataobjects.shared.QueryInfo;
import com.sri.ai.lpitools.cobra.gwtutil.dataobjects.shared.UserInfo;
import com.sri.ai.lpitools.gwtgui.client.RPCService;
import com.sri.ai.lpitools.gwtgui.client.RPCServiceAsync;
import com.sri.ai.lpitools.gwtgui.client.Toolkit;
import com.sri.ai.lpitools.gwtgui.client.components.dialogs.MessageDialog;
import com.sri.ai.lpitools.gwtgui.client.components.dialogs.NewUserDialog;
import com.sri.ai.lpitools.gwtgui.client.components.editor.EvidenceEditor;
import com.sri.ai.lpitools.gwtgui.client.components.manager.AICManager;
import com.sri.ai.lpitools.gwtgui.client.components.manager.EvidenceManager;
import com.sri.ai.lpitools.gwtgui.client.components.manager.ModelManager;
import com.sri.ai.lpitools.gwtgui.client.components.manager.UserManager;
import com.sri.ai.lpitools.gwtgui.client.util.JSONUtil;

/**
 * AdminPanel for performing administrative tasks, such as User and Data management
 * @author bellwein
 */
public class AdminPanel extends SplitLayoutPanel {

	@SuppressWarnings("unused")
	private AICManager aicManager;
	private RPCServiceAsync rpc;
	private UserManager userManager;
	private ModelManager modelManager;
	
	private CellTable<ModelInfo> modelTable;
	private SingleSelectionModel<ModelInfo> modelSelectionModel;
	
	private CellTable<QueryInfo> queryTable;
	private SingleSelectionModel<QueryInfo> querySelectionModel;
	
	private CellTable<EvidenceInfo> evidenceTable;
	private SingleSelectionModel<EvidenceInfo> evidenceSelectionModel;
	
	private Button addNewUser;
	
	private CellTable<UserInfo> usersTable;
	private SingleSelectionModel<UserInfo> userSelectionModel;
	
	private EvidenceEditor editor;
	private SimpleLayoutPanel rightPanel;
	private EvidencePanel ep;
	
	public AdminPanel(Toolkit toolkit) {
		super(5);
		this.aicManager = toolkit.getAicManager();
		this.rpc = RPCService.Util.getInstance();
		this.userManager = toolkit.getUserManager();
		this.modelManager = toolkit.getModelManager();
		
		EvidenceManager em = new EvidenceManager();
		editor = new EvidenceEditor(em, false);
		
		ep = new EvidencePanel(userManager, editor, this, em);	
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// build users table
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		this.usersTable = new CellTable<UserInfo>();
		this.addNewUser = new Button("Create New User");
		addNewUser.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				newUser();
			}
		});		
		userSelectionModel = new SingleSelectionModel<UserInfo>();
		usersTable.setSelectionModel(userSelectionModel);
		
		//add the username column
		Column<UserInfo, String> usernameColumn = new Column<UserInfo, String>(new TextCell()) {
			public String getValue(UserInfo object) {
				String user = object.getUser();
				return user;
			}
		};
		usersTable.addColumn(usernameColumn, "Username");
		
		//add an admin checkbox column
		Column<UserInfo, Boolean> adminColumn = new Column<UserInfo, Boolean>(new CheckboxCell(false, false)) {
			public Boolean getValue(UserInfo object) {
				return object.isAdmin();
			}
		};
		usersTable.addColumn(adminColumn, "Admin");
		adminColumn.setFieldUpdater(new FieldUpdater<UserInfo, Boolean>() {
			@Override
			public void update(int index, UserInfo object, Boolean value) {
				object.setAdmin(value);
				updateUser(object);
			}
		});
		
		//delete column
		ButtonCell deleteButtonCell = new ButtonCell();
		Column<UserInfo, String> deleteColumn = new Column<UserInfo, String>(deleteButtonCell) {
			@Override
			public String getValue(UserInfo object) {
				return "Delete";
			}
		};
		
		usersTable.addColumn(deleteColumn);
		deleteColumn.setFieldUpdater(new FieldUpdater<UserInfo, String>() {
			@Override
			public void update(int index, UserInfo object, String value) {
				deleteUser(object);
			}
		});
		
		usersTable.setColumnWidth(adminColumn, "20px");
		usersTable.setColumnWidth(deleteColumn, "60px");
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// build models table
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		modelTable = new CellTable<ModelInfo>();
		//add a model name 
		Column<ModelInfo, String> titleColumn = new Column<ModelInfo, String>(new TextCell()) {
			@Override
			public String getValue(ModelInfo object) {
				return object.getModelTitle();
			}
		};
		modelTable.addColumn(titleColumn, "Title");
		
		//delete column
		ButtonCell deleteModelButtonCell = new ButtonCell();
		Column<ModelInfo, String> deleteModelColumn = new Column<ModelInfo, String>(deleteModelButtonCell) {
			@Override
			public String getValue(ModelInfo object) {
				return "Delete";
			}
		};
		
		modelTable.addColumn(deleteModelColumn);
		deleteModelColumn.setFieldUpdater(new FieldUpdater<ModelInfo, String>() {
			@Override
			public void update(int index, ModelInfo object, String value) {
				rpc.deleteModelByKey(userManager.getCurrentUser().getUser(), object.getModelTitle(), new AsyncCallback<Boolean>() {
					@Override
					public void onFailure(Throwable caught) {
						MessageDialog.showMessageDialog(caught.getMessage());
					}
					@Override
					public void onSuccess(Boolean result) {
						if (!result) {
							MessageDialog.showMessageDialog("Unable to delete model");
						}
						populateModels(userSelectionModel.getSelectedObject());
						rightPanel.clear();
					}
				});
			}
		});
		
		modelTable.setColumnWidth(deleteModelColumn, "60px");
		
		modelSelectionModel = new SingleSelectionModel<ModelInfo>();
		modelTable.setSelectionModel(modelSelectionModel);
		modelSelectionModel.addSelectionChangeHandler(new Handler() {
			@Override
			public void onSelectionChange(SelectionChangeEvent event) {
				rightPanel.clear();
				ModelInfo model = modelSelectionModel.getSelectedObject();
				if (model != null) {
					ModelPanel mp = new ModelPanel(modelManager, userManager, model, model.getModelText(), "Edit Model", AdminPanel.this);
					rightPanel.add(mp);
				}
			}
		});

		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// build queries table
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		queryTable = new CellTable<QueryInfo>();
		//add a model name 
		Column<QueryInfo, String> queryNameColumn = new Column<QueryInfo, String>(new TextCell()) {
			@Override
			public String getValue(QueryInfo object) {
				return object.getQueryTitle();
			}
		};
		queryTable.addColumn(queryNameColumn, "Name");
		
		//delete column
		ButtonCell deleteQueryButtonCell = new ButtonCell();
		Column<QueryInfo, String> deleteQueryButtonColumn = new Column<QueryInfo, String>(deleteQueryButtonCell) {
			@Override
			public String getValue(QueryInfo object) {
				return "Delete";
			}
		};
		
		queryTable.addColumn(deleteQueryButtonColumn);
		deleteQueryButtonColumn.setFieldUpdater(new FieldUpdater<QueryInfo, String>() {
			@Override
			public void update(int index, QueryInfo object, String value) {
				rpc.deleteQuery(userManager.getCurrentUser().getUser(), object.getQueryTitle(), new AsyncCallback<Boolean>() {
					@Override
					public void onFailure(Throwable caught) {
						MessageDialog.showMessageDialog(caught.getMessage());
					}
					@Override
					public void onSuccess(Boolean result) {
						if (!result) {
							MessageDialog.showMessageDialog("unable to delete query");
						}
						populateQueries(userSelectionModel.getSelectedObject());
						rightPanel.clear();
					}
				});
			}
		});
		
		queryTable.setColumnWidth(deleteQueryButtonColumn, "60px");
		
		querySelectionModel = new SingleSelectionModel<QueryInfo>();
		queryTable.setSelectionModel(querySelectionModel);
		querySelectionModel.addSelectionChangeHandler(new Handler() {
			@Override
			public void onSelectionChange(SelectionChangeEvent event) {
				rightPanel.clear();
				QueryInfo query = querySelectionModel.getSelectedObject();
				if (query != null) {
					QueryPanel qp = new QueryPanel(query, userManager, AdminPanel.this);
					rightPanel.add(qp);
				}
			}
		});

		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// build evidence table
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		evidenceTable = new CellTable<EvidenceInfo>();
		//add a model name 
		Column<EvidenceInfo, String> evidenceNameColumn = new Column<EvidenceInfo, String>(new TextCell()) {
			@Override
			public String getValue(EvidenceInfo object) {
				return object.getEvidenceTitle();
			}
		};
		evidenceTable.addColumn(evidenceNameColumn, "Title");
		
		//delete column
		ButtonCell deleteEvidenceButtonCell = new ButtonCell();
		Column<EvidenceInfo, String> deleteEvidenceButtonColumn = new Column<EvidenceInfo, String>(deleteEvidenceButtonCell) {
			@Override
			public String getValue(EvidenceInfo object) {
				return "Delete";
			}
		};
		
		evidenceTable.addColumn(deleteEvidenceButtonColumn);
		deleteEvidenceButtonColumn.setFieldUpdater(new FieldUpdater<EvidenceInfo, String>() {
			@Override
			public void update(int index, EvidenceInfo object, String value) {
				rpc.deleteEvidence(userManager.getCurrentUser().getUser(), object.getEvidenceTitle(), new AsyncCallback<Boolean>() {
					@Override
					public void onFailure(Throwable caught) {
						MessageDialog.showMessageDialog(caught.getMessage());
					}
					@Override
					public void onSuccess(Boolean result) {
						populateEvidences(userSelectionModel.getSelectedObject());
						rightPanel.clear();
					}
				});
			}
		});
		
		evidenceTable.setColumnWidth(deleteEvidenceButtonColumn, "60px");
		
		evidenceSelectionModel = new SingleSelectionModel<EvidenceInfo>();
		evidenceTable.setSelectionModel(evidenceSelectionModel);
		evidenceSelectionModel.addSelectionChangeHandler(new Handler() {
			@Override
			public void onSelectionChange(SelectionChangeEvent event) {
				rightPanel.clear();
				EvidenceInfo evidence = evidenceSelectionModel.getSelectedObject();
				if (evidence != null) {
					rightPanel.add(ep);
					ep.setEvidenceObject(evidence);
				}
			}
		});

		modelTable.setWidth("100%");
		queryTable.setWidth("100%");
		evidenceTable.setWidth("100%");
		usersTable.setWidth("100%");
				
		//add a selection changed listener to update the list views of data for that user
		userSelectionModel.addSelectionChangeHandler(new Handler() {
			@Override
			public void onSelectionChange(SelectionChangeEvent event) {
				UserInfo userInfo = userSelectionModel.getSelectedObject();
				populateModels(userInfo);
				populateEvidences(userInfo);
				populateQueries(userInfo);
			}
		});
		
		//layout the components
		StackLayoutPanel lowerStackPanel = new StackLayoutPanel(Unit.EM);
		lowerStackPanel.setSize("100%", "100%");
		lowerStackPanel.add(modelTable, "Models", 2);
		lowerStackPanel.add(queryTable, "Queries", 2);
		lowerStackPanel.add(evidenceTable, "Evidences", 2);
		
		StackLayoutPanel upperStackPanel = new StackLayoutPanel(Unit.EM);
		DockLayoutPanel userLayoutPanel = new DockLayoutPanel(Unit.EM);
		userLayoutPanel.addNorth(addNewUser, 2);
		userLayoutPanel.add(usersTable);
		upperStackPanel.add(userLayoutPanel, "Users", 2);
		
		SplitLayoutPanel slp = new SplitLayoutPanel();
		slp.addNorth(upperStackPanel, 400);
		slp.add(lowerStackPanel);
		
		rightPanel = new SimpleLayoutPanel();
		
		this.addWest(slp,320);

		this.add(rightPanel);
		this.populateUsers();
		
		usersTable.getSelectionModel().setSelected(userManager.getCurrentUser(), true);
	}

	protected void updateUser(UserInfo currentUser) {
		if(!currentUser.getUser().equalsIgnoreCase(userManager.getCurrentUser().getUser())){
			AICManager.saveUser(currentUser);
			populateUsers();
		} else {
			MessageDialog.showMessageDialog("You can't update your own Admin powers");
		}
	}

	protected void newUser(){
		NewUserDialog.showNewUserDialog(this);
		populateUsers();
	}
	
	protected void deleteUser(UserInfo currentUser) {
		if(!currentUser.getUser().equalsIgnoreCase(userManager.getCurrentUser().getUser())){
			AICManager.deleteUser(currentUser.getUser());
			populateUsers();
		} else {
			MessageDialog.showMessageDialog("You can't delete Yourself");
		}
	}

	public void clearRightPanel(){
		rightPanel.clear();
	}
	
	/**
	 * populate the models list with the models for the specified user
	 * @param user
	 */
	public void populateModels(UserInfo user) {
		//request the list of models from RPC
		rpc.getModelList(user.getUser(), new AsyncCallback<String>() {
			List<String> models = new ArrayList<String>();
			@Override
			public void onSuccess(String result) {
				if (result == null) {
					MessageDialog.showMessageDialog("Null response for getModelList query");
					return;
				}
				if(result.startsWith("ERROR.")){
					models.add(result);
				} else {
					List<ModelInfo> list = JSONUtil.getModelInfoList(result);
					modelTable.setRowData(list);
				}
			}

			@Override
			public void onFailure(Throwable caught) {
				MessageDialog.showMessageDialog(caught.getMessage());
			}
		});
	}
	
	/**
	 * populate the queries list with the queries for the selected user
	 * @param user
	 */
	public void populateQueries(UserInfo user) {

		//request the list of models from RPC
		rpc.getQueryList(user.getUser(), new AsyncCallback<String>() {
			List<String> queries = new ArrayList<String>();
			@Override
			public void onSuccess(String result) {
				
				if (result == null) {
					MessageDialog.showMessageDialog("Null response for getQueryList query");
					return;
				}
				if(result.startsWith("ERROR.")){
					queries.add(result);
				} else {
					List<QueryInfo> list = JSONUtil.getQueryInfoList(result);
					queryTable.setRowData(list);
				}
			}

			@Override
			public void onFailure(Throwable caught) {
				MessageDialog.showMessageDialog(caught.getMessage());
			}
		});
	}
	
	public void populateEvidences(UserInfo user) {
		//request the list of models from RPC
		rpc.getEvidenceList(user.getUser(), new AsyncCallback<String>() {
			List<String> evidences = new ArrayList<String>();
			@Override
			public void onSuccess(String result) {
				if (result == null) {
					MessageDialog.showMessageDialog("Null response for getEvidenceList query");
					return;
				}
				if(result.startsWith("ERROR.")){
					evidences.add(result);
				} else {
					List<EvidenceInfo> list = JSONUtil.getEvidenceInfoList(result);
					evidenceTable.setRowData(list);
				}
			}

			@Override
			public void onFailure(Throwable caught) {
				MessageDialog.showMessageDialog(caught.getMessage());
			}
		});
	}
	
	public void populateUsers() {
		if(userManager.getCurrentUser().isAdmin()){
			addNewUser.setVisible(true);
			usersTable.setVisible(true);
			//request the list of models from RPC
			rpc.listUsers(new AsyncCallback<String>() {
				List<String> users = new ArrayList<String>();
				@Override
				public void onSuccess(String result) {
					if (result == null) {
						MessageDialog.showMessageDialog("Null response for getEvidenceList query");
						return;
					}
					if(result.startsWith("ERROR.")){
						users.add(result);
					} else {
						List<UserInfo> list = JSONUtil.getUserInfoList(result);
						usersTable.setRowData(list);
					}
	
				}
	
				@Override
				public void onFailure(Throwable caught) {
					MessageDialog.showMessageDialog(caught.getMessage());
	
				}
			});
		}
		else{	
			addNewUser.setVisible(false);
			usersTable.setVisible(false);
			populateModels(userManager.getCurrentUser());
			populateQueries(userManager.getCurrentUser());
			populateEvidences(userManager.getCurrentUser());
		}
	}	
	
	public void updateUserData(){
		populateModels(userManager.getCurrentUser());
		populateQueries(userManager.getCurrentUser());
		populateEvidences(userManager.getCurrentUser());
	}
	
}
